#include "./../Headers/matrix_1d.h"

#ifdef ALLOW_DEBUGS
#define DEBUG_MAT_1D
#endif

namespace Mathematics {
	namespace Matrix {
		
			// -static private methods-
			
			// -private methods-
		
/*		class ApplyFunc:public Functor {
		protected:
			Matrix1D *mat;
			const Matrix1D *mat2;
			usint start, end;
			
		public:
			CopyFunc( Matrix1D *m, const Matrix1D &m2, const usint &start_index, const usint &end_index ):
				mat(m), mat2(&m2), start(start_index), end(end_index) {
			};
			CopyFunc( const CopyFunc &org ):
				mat(org.mat), mat2(org.mat2), start(org.start), end(org.end) {
			};
			~CopyFunc() {
			};
			
			virtual void operator() (){
				for(register usint i = start; i < end; ++i){
					mat->value[i] = mat2->value[i];
				}
			};

		};
*/		
		// copies each value
		void Matrix1D::copy(const Matrix1D &original){
/**		class CopyFunc:public Functor {
			protected:
				Matrix1D *mat;
				const Matrix1D *mat2;
				usint start, end;
			public:
				CopyFunc( Matrix1D *m, const Matrix1D &m2, const usint &start_index, const usint &end_index ):
					mat(m), mat2(&m2), start(start_index), end(end_index) {
				};
				CopyFunc( const CopyFunc &org ):
					mat(org.mat), mat2(org.mat2), start(org.start), end(org.end) {
				};
				~CopyFunc() {
				};
				
				virtual void operator() (){
					for(register usint i = start; i < end; ++i){
						mat->value[i] = mat2->value[i];
					}
				};

			};

			deque< Functor* > routines;
			usint n_thread = 2*num_core;
			for( usint i = 0; i < n_thread; ++i ){
				routines.push_back( new CopyFunc( &(*this), original, i*dim/n_thread, (i+1)*dim/n_thread ) );
			}
			ThreadLauncher( routines );
			
/**/
/**/
			for(register usint i = 0; i < dim; ++i){
				value[i] = original.value[i];
			}
/**/
		};
		// mallocs the value
		bool Matrix1D::create(){
			value = (double*) malloc(dim * sizeof(double));
			(*this) *= 0.0;
			return value != NULL;
		};
		// frees the value
		void Matrix1D::free(){
			//if(value != NULL){
			//	delete[] value;
			//	value = NULL;
			//}
			//dim = 0;
		};
		
		// hidden builder for creating alias
		Matrix1D::Matrix1D():
			Wtb(), id(assignId()),copied(false),dim(0),value(NULL){
		#ifdef DEBUG_MAT_1D
			debug_function_info( "Hidden Constructor: ", "Matrix1D", (int) &( *this ), sizeof( *this ), id );
			cout << "Dimension: " << dim << pendl << endl;
		#endif
		};
		
			// -constructors-
		Matrix1D::Matrix1D(const usint &dimension):
			Wtb(), id(assignId()),copied(false),dim(dimension),value(NULL){
		#ifdef DEBUG_MAT_1D
			debug_function_info( "Constructor: ", "Matrix1D", (int) &( *this ), sizeof( *this ), id );
			cout << "Dimension: " << dim << pendl << endl;
		#endif
			
			if(!create()){
				cout << endl << "Problem while creating Matrix1D." << endl;
				exit(-1);
			};
		};
		
		Matrix1D::Matrix1D(const Matrix1D &original):
			Wtb(), id(assignId()),copied(false),dim(original.dim),value(NULL){
		#ifdef DEBUG_MAT_1D
			debug_function_info( "Copy Constructor: ", "Matrix1D", (int) &( *this ), sizeof( *this ), id );
			cout << "Dimension: " << dim << pendl << endl;
		#endif
			if(!create()){
				cout << endl << "Problem while creating Matrix1D." << endl;
				exit(-1);
			};
			copy(original);
		};
		
		Matrix1D::~Matrix1D(){
		#ifdef DEBUG_MAT_1D
			debug_function_info( "Destructor: ", "Matrix1D", (int) &( *this ), sizeof( *this ), id );
			cout << "Dimension: " << dim << pendl << endl;
		#endif
		
			if(!copied){ // impede que uma area de memoria seja destruida por alias
		#ifdef DEBUG_MAT_1D
				cout << "Original." << endl;
		#endif
				free();
			}
		};
		
			// -getters & setters-
		Matrix1D Matrix1D::getSelection(const usint &index, const usint &size) const {
			if(index+size > dim)
				throw OutOfBoundsException("Invalid access.",dim,index+size-1);

			Matrix1D ret_value(size);
			for(register usint i = 0; i < size; ++i)
				ret_value.value[i] = value[index+i];

			return ret_value;
		};
		void Matrix1D::setSelection(const usint &index, const Matrix1D &part){
			if(index+part.dim > dim)
				throw OutOfBoundsException("Invalid access.",dim,index+part.dim-1);

			for(register usint i = 0; i < part.dim; ++i)
				value[index+i] = part.value[i];

		};
		void Matrix1D::setSelection(const Matrix1D &part){
			if(part.dim > dim){
				throw OutOfBoundsException("Invalid access.",dim,part.dim-1);
			}
			for(register usint i = 0; i < part.dim; ++i){
				value[i] = part.value[i];
			}
		};
		
			// -operators-
		Matrix1D& Matrix1D::operator = (const Matrix1D &mat){
			if(dim != mat.dim)
				throw IncompatibleMatrices1DException( "Invalid attribution. Matrices must have same dimensions.", (*this), mat );

			copy(mat);
			return (*this);
		};
		Matrix1D Matrix1D::operator + (const Matrix1D &mat) const {
			if(dim != mat.dim)
				throw IncompatibleMatrices1DException( "Invalid addition. Matrices must have same dimensions.", (*this), mat );

			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i){
				result.value[i] = value[i] + mat.value[i];
			}
			return result;
		};
		Matrix1D& Matrix1D::operator += (const Matrix1D &mat){
			if(dim != mat.dim)
				throw IncompatibleMatrices1DException( "Invalid addition. Matrices must have same dimensions.", (*this), mat );

			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i){
				value[i] += mat.value[i];
			}
			return (*this);
		};
		Matrix1D Matrix1D::operator - (const Matrix1D &mat) const {
			if(dim != mat.dim)
				throw IncompatibleMatrices1DException( "Invalid subtraction. Matrices must have same dimensions.", (*this), mat );

			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i){
				result.value[i] = value[i] - mat.value[i];
			}
			return result;
		};
		Matrix1D& Matrix1D::operator -= (const Matrix1D &mat){
			if(dim != mat.dim)
				throw IncompatibleMatrices1DException( "Invalid subtraction. Matrices must have same dimensions.", (*this), mat );

			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i){
				value[i] -= mat.value[i];
			}
			return (*this);
		};
		Matrix1D Matrix1D::operator * (const double &scalar) const {
			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i){
				result.value[i] = value[i] * scalar;
			}
			return result;
		};
		Matrix1D& Matrix1D::operator *= (const double &scalar){
			for(register usint i = 0; i < dim; ++i){
				value[i] *= scalar;
			}
			return (*this);
		};
		Matrix1D Matrix1D::operator / (const double &scalar) const {
			if(scalar == 0.0)
				throw DivisionByZeroException<Matrix1D>("Invalid operation. Division by zero!", *(this));

			Matrix1D result(dim);
			for(register usint i = 0; i < dim; ++i)
				result.value[i] = value[i] / scalar;

			return result;
		};
		Matrix1D& Matrix1D::operator /= (const double &scalar){
			if(scalar == 0.0)
				throw DivisionByZeroException<Matrix1D>("Invalid operation. Division by zero!", *(this));

			for(register usint i = 0; i < dim; ++i)
				value[i] /= scalar;

			return (*this);
		};
		Matrix1D& Matrix1D::operator () (const usint &begin_index, const usint &end_index) {
			if(begin_index > end_index){ // if dumb user
				return (*this)(end_index,begin_index);
				//return this->operator()(end_index,begin_index);
			}
			if(end_index >= dim){
				throw OutOfBoundsException("Invalid access.",dim,end_index);
			}
			
			if(copied){
				dim = end_index - begin_index + 1;
				value = &( value[ begin_index ] );
				return *this;
			}
			else{
				Matrix1D *temp = new Matrix1D();
				temp->copied = true;
				temp->dim = end_index - begin_index + 1;
				temp->value = &( value[ begin_index ] );
				return (*temp);
			}
			
			return *this;
		};
		Matrix1D& Matrix1D::operator , (const double &num){
			if(dim == 0){
				throw OutOfBoundsException("Invalid access.",dim,0);
			}
			
			if(copied){
				value[0] = num;
				--dim;
				value = &(value[1]);
				return *this;
			}
			else{
				Matrix1D *temp = new Matrix1D();
				temp->copied = true;
				value[0] = num;
				temp->dim = dim-1;
				temp->value = &(value[1]);
				return *temp;
			}
		};
		Matrix1D& Matrix1D::operator , (const MatrixManipulation &action){
			if(dim == 0){
				throw OutOfBoundsException("Invalid access.",dim,0);
			}
			if(action == pr){
				if(copied){
					--dim;
					value = &(value[1]);
					return *this;
				}
				else{
					Matrix1D *temp = new Matrix1D();
					temp->copied = true;
					temp->dim = dim-1;
					temp->value = &(value[1]);
					return *temp;
				}
			}
			return *this;
		};
		double& Matrix1D::operator [] (const usint &index) const {
			if(index < 0 || index > dim){
				throw OutOfBoundsException("Invalid access.",dim,index);
			}
			return value[index];
		};
		
			// -metodos internos publicos-
		
			// -leitura & escrita-
		string Matrix1D::toS() const {
			string ret_value = "[ ";
			for(register usint i = 0; i < dim; ++i){
				ret_value += dtos( value[i] ) + " ";
			}
			return ret_value + "]";
			
		};
		
		bool Matrix1D::filePrint( FILE *parq ) const {
			return fprintf( parq, "%s", toS().c_str() ) == 1;
		};
		
			
		double* Matrix1D::toPointer() const {
			double *mat = NULL;
			mat = (double*) malloc( dim*sizeof(double) );
			if(mat == NULL){
				cout << "Couldn't allocate memmory!" << endl;
				exit(-1);
			}
			for(register usint i = 0; i < dim; ++i)
				mat[i] = value[i];
			return mat;
		};
		
		bool Matrix1D::operator == ( const Matrix1D &other ) const {
			if( this->dim != other.dim )
				return false;
			for( usint i = 0; i < dim; ++i )
				if( Mod( value[i] - other.value[i] ) > 0.001 )
					return false;
			return true;
		};

		// declaring static variable's memory position
		usint Matrix1D::next_id;
		
	};
};
